package com.wyk.project.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wyk.project.common.convention.exception.ClientException;
import com.wyk.project.common.convention.exception.ServiceException;
import com.wyk.project.common.enums.VailDateTypeEnum;
import com.wyk.project.dao.entity.ShortLinkDO;
import com.wyk.project.dao.entity.ShortLinkGotoDO;
import com.wyk.project.dao.mapper.ShortLinkGotoMapper;
import com.wyk.project.dao.mapper.ShortLinkMapper;
import com.wyk.project.dto.biz.ShortLinkStatsRecordDTO;
import com.wyk.project.dto.req.ShortLinkBatchCreateReqDTO;
import com.wyk.project.dto.req.ShortLinkCreateReqDTO;
import com.wyk.project.dto.req.ShortLinkPageReqDTO;
import com.wyk.project.dto.req.ShortLinkUpdateReqDTO;
import com.wyk.project.dto.resp.*;
import com.wyk.project.mq.producer.ShortLinkStatsSaveProducer;
import com.wyk.project.service.ShortLinkService;
import com.wyk.project.util.HashUtil;
import com.wyk.project.util.LinkUtil;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static com.wyk.project.common.constant.RedisKeyConstant.*;

/**
 * 短连接接口实现层
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class ShortLinkServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLinkDO> implements ShortLinkService {


    private final RBloomFilter<String> shortUriCreateCachePenetrationBloomFilter;

    private final ShortLinkGotoMapper shortLinkGotoMapper;

    private final StringRedisTemplate stringRedisTemplate;

    private final RedissonClient redissonClient;

    private final ShortLinkStatsSaveProducer shortLinkStatsSaveProducer;


    @Value("${short-link.stats.locale.amap-key}")
    private  String statsLocalamapKey;

    @Value("${short-link.domain.default}")
    private  String createShortLinkDefalutDomain;

    /**
     * 创建短连接
     *
     * @param shortLinkCreateReqDTO
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public ShortLinkCreateRespDTO createShortLink(ShortLinkCreateReqDTO shortLinkCreateReqDTO) {
        String shortlinkSuffx = generateSuffix(shortLinkCreateReqDTO);
        //短连接的Buder
        ShortLinkDO shortLinkDO = BeanUtil.toBean(shortLinkCreateReqDTO, ShortLinkDO.class);
        //这个 前端传的 Domain不带 http ，full_short_link就没http
        String full_short_link = createShortLinkDefalutDomain + "/" + shortlinkSuffx;
        shortLinkDO.setShortUri(shortlinkSuffx);
        shortLinkDO.setDomain(createShortLinkDefalutDomain);
        shortLinkDO.setEnableStatus(0);
        shortLinkDO.setFavicon(getFavicon(shortLinkCreateReqDTO.getOriginUrl()));
        shortLinkDO.setDelTime(0L);
        shortLinkDO.setFullShortUrl(createShortLinkDefalutDomain + "/" + shortlinkSuffx);
        shortLinkDO.setTotalPv(0);
        shortLinkDO.setTotalUv(0);
        shortLinkDO.setTotalUip(0);
        //短连接路由Buder
        ShortLinkGotoDO shortLinkGotoDO = ShortLinkGotoDO.builder()
                .fullShortUrl(full_short_link)
                .gid(shortLinkCreateReqDTO.getGid()).build();
        try {
            baseMapper.insert(shortLinkDO);
            shortLinkGotoMapper.insert(shortLinkGotoDO);
            //   basemapper的插入： 这个异常是 插入mysql 是 key重复了，高并发避免问题咯
            //    存在的 判断为 不存在
        } catch (DuplicateKeyException ex) {
            //  布隆过滤器特性：● 查询是否存在，如果返回存在，可能数据是不存在的。
            //               ● 查询是否存在，如果返回不存在，数据一定不存在。
                if(!shortUriCreateCachePenetrationBloomFilter.contains(full_short_link)) {
                    shortUriCreateCachePenetrationBloomFilter.add(full_short_link);
                }
                log.warn("短链接：{} 重复入库", full_short_link);
                throw new ServiceException(String.format("短连接：%s,生成重复",full_short_link));
        }
        stringRedisTemplate.opsForValue().set(
                String.format(GOTO_SHORT_LINK_KEY,full_short_link),
                shortLinkCreateReqDTO.getOriginUrl(),
                LinkUtil.getLinCacheValidDate(shortLinkCreateReqDTO.getValidDate()),
                TimeUnit.MILLISECONDS
        );
        shortUriCreateCachePenetrationBloomFilter.add(full_short_link);
        return ShortLinkCreateRespDTO.builder()
                .fullShortUrl("http://" + shortLinkDO.getFullShortUrl())
                .originUrl(shortLinkCreateReqDTO.getOriginUrl())
                .gid(shortLinkCreateReqDTO.getGid())
                .build();
    }

    /**
     * 分页查询短连接
     *
     * @param shortLinkPageReqDTO
     * @return
     */
    @Override
    public IPage<ShortLinkPageRespDTO> pageshortLink(ShortLinkPageReqDTO shortLinkPageReqDTO) {
        IPage<ShortLinkDO> resultPage = baseMapper.pageLink(shortLinkPageReqDTO);
        return resultPage.convert(page -> {
            ShortLinkPageRespDTO pageRespDTO = BeanUtil.toBean(page, ShortLinkPageRespDTO.class);
            pageRespDTO.setDomain("http://" + pageRespDTO.getDomain());
            return pageRespDTO;
        });
    }

    private String generateSuffix(ShortLinkCreateReqDTO shortLinkCreateReqDTO) {
        int count = 0;
        String shortUri;
        while (true) {
            if (count > 10) {
                throw new ServiceException("短链接创造频繁，请稍后再试！");
            }
            //加上UUID随机数，减少重复可能 ，比当前毫秒数更可靠，避免同一时间并发的请求
            shortUri = HashUtil.hashToBase62(shortLinkCreateReqDTO.getOriginUrl() + UUID.randomUUID().toString());
            if (!shortUriCreateCachePenetrationBloomFilter.contains(createShortLinkDefalutDomain + "/" + shortUri)) {
                break;
            }
            count++;
        }
        return shortUri;
    }

    /**
     * 查询短链接分组内数量
     *
     * @return
     */
    @Override
    public List<ShortLinkCountQuerRespDTO> listGroupShortLinkCount(List<String> requestParam) {
        QueryWrapper<ShortLinkDO> queryWrapper = Wrappers.query(new ShortLinkDO())
                .select("gid as gid, count(*) as shortLinkCount")
                .in("gid", requestParam)
                .eq("enable_status", 0)
                .eq("del_flag", 0)
                .eq("del_time", 0L)
                .groupBy("gid");
        List<Map<String, Object>> shortLinkDOList = baseMapper.selectMaps(queryWrapper);
        return BeanUtil.copyToList(shortLinkDOList, ShortLinkCountQuerRespDTO.class);
    }

    /**
     * 修改短链接
     *
     * @param requestParam
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateShortLink(ShortLinkUpdateReqDTO requestParam) {
        //先查询修改的连接是否 存在
        ShortLinkDO hasShortLinkDO = baseMapper.selectOne(Wrappers.lambdaQuery(ShortLinkDO.class)
                .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                .eq(ShortLinkDO::getGid, requestParam.getOriginGid())
                .eq(ShortLinkDO::getDelFlag, 0)
                .eq(ShortLinkDO::getEnableStatus, 0));
        //不存在
        if (hasShortLinkDO == null) {
            throw new ClientException("短链接不存在！");
        }
        //修改后的短链接 分组标识没有变化 ，路由表也不用变惹
        if (Objects.equals(hasShortLinkDO.getGid(), requestParam.getGid())) {
            //gid 一致
            LambdaUpdateWrapper<ShortLinkDO> updateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
                    .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                    .eq(ShortLinkDO::getGid, requestParam.getGid())
                    .eq(ShortLinkDO::getDelFlag, 0)
                    .eq(ShortLinkDO::getEnableStatus, 0)
                    .set(Objects.equals(requestParam.getValidDateType(),
                            VailDateTypeEnum.PERMANENT.getType()), ShortLinkDO::getValidDate, null);
            ShortLinkDO build = ShortLinkDO.builder()
                    .gid(requestParam.getGid())
                    .domain(hasShortLinkDO.getDomain())
                    .shortUri(hasShortLinkDO.getShortUri())
                    .clickNum(hasShortLinkDO.getClickNum())
                    .favicon(hasShortLinkDO.getFavicon())
                    .createdType(hasShortLinkDO.getCreatedType())
                    .describe(requestParam.getDescribe())
                    .originUrl(requestParam.getOriginUrl())
                    .validDate(requestParam.getValidDate())
                    .validDateType(requestParam.getValidDateType())
                    .build();
            baseMapper.update(build, updateWrapper);
        } else {
            //拿到写锁
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(String.format(LOCK_GID_UPDATE_KEY, requestParam.getFullShortUrl()));
            RLock rLock = readWriteLock.writeLock();
            rLock.lock();
            try {
                //修改短链接 设置del_time 和 del_flag （逻辑删除）
                LambdaUpdateWrapper<ShortLinkDO> linkUpdateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
                        .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                        .eq(ShortLinkDO::getGid, hasShortLinkDO.getGid())
                        .eq(ShortLinkDO::getDelFlag, 0)
                        .eq(ShortLinkDO::getDelTime, 0L)
                        .eq(ShortLinkDO::getEnableStatus, 0);
                ShortLinkDO delShortLinkDO = ShortLinkDO.builder()
                        .delTime(System.currentTimeMillis())
                        .build();
                delShortLinkDO.setDelFlag(1);
                baseMapper.update(delShortLinkDO, linkUpdateWrapper);
                //  对应上一步的逻辑删除， 把修改后的新的短链接插入表中
                ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                        .domain(createShortLinkDefalutDomain)
                        .originUrl(requestParam.getOriginUrl())
                        .gid(requestParam.getGid())
                        .createdType(hasShortLinkDO.getCreatedType())
                        .validDateType(requestParam.getValidDateType())
                        .validDate(requestParam.getValidDate())
                        .describe(requestParam.getDescribe())
                        .shortUri(hasShortLinkDO.getShortUri())
                        .enableStatus(hasShortLinkDO.getEnableStatus())
                        .totalPv(hasShortLinkDO.getTotalPv())
                        .totalUv(hasShortLinkDO.getTotalUv())
                        .totalUip(hasShortLinkDO.getTotalUip())
                        .fullShortUrl(hasShortLinkDO.getFullShortUrl())
                        .favicon(getFavicon(requestParam.getOriginUrl()))
                        .delTime(0L)
                        .build();
                baseMapper.insert(shortLinkDO);

                // 短链接--路由表信息删除老信息,更换新信息
                LambdaQueryWrapper<ShortLinkGotoDO> linkGotoQueryWrapper = Wrappers.lambdaQuery(ShortLinkGotoDO.class)
                        .eq(ShortLinkGotoDO::getFullShortUrl, requestParam.getFullShortUrl())
                        .eq(ShortLinkGotoDO::getGid, hasShortLinkDO.getGid());
                ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(linkGotoQueryWrapper);
                shortLinkGotoMapper.delete(linkGotoQueryWrapper);
                shortLinkGotoDO.setGid(requestParam.getGid());
                shortLinkGotoMapper.insert(shortLinkGotoDO);
            } finally {
                rLock.unlock();
            }
        }
            // 修改的有效期变化了 删除缓存，为了解决有限期变更为过期时间 仍然跳转的问题
            if (!Objects.equals(hasShortLinkDO.getValidDateType(), requestParam.getValidDateType())
                    || !Objects.equals(hasShortLinkDO.getValidDate(), requestParam.getValidDate())
                    || !Objects.equals(hasShortLinkDO.getOriginUrl(),requestParam.getOriginUrl())) {
                stringRedisTemplate.delete(String.format(GOTO_SHORT_LINK_KEY, requestParam.getFullShortUrl()));
                // 要修改的链接是过期的
                if (hasShortLinkDO.getValidDate() != null && hasShortLinkDO.getValidDate().before(new Date())) {
                    // 修改后的是永久有效或者不过期的
                    if (Objects.equals(requestParam.getValidDateType(), VailDateTypeEnum.PERMANENT.getType()) || requestParam.getValidDate().after(new Date())) {
                        stringRedisTemplate.delete(String.format(GOTO_IS_NOLL_SHORT_LINK_KEY, requestParam.getFullShortUrl()));
                    }
                }
            }
        }

    /**
     * 短连接跳转
     *
     * @param shortUri
     * @param request
     * @param response
     */
    @SneakyThrows
    @Override
    public void restoreUrl(String shortUri, ServletRequest request, ServletResponse response) {
        String serverName = request.getServerName();
        String serverPort = Optional.of(request.getServerPort())
                .filter(each -> !Objects.equals(each, 80))
                .map(String::valueOf)
                .map(each -> ":" + each)
                .orElse("");
        String full_short_url = serverName + serverPort + "/" + shortUri;
        //1.先从缓存中那 跳转的原始链接 拿到的话直接跳转
        String origin_url = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, full_short_url));
        if (StrUtil.isNotBlank(origin_url)) {
            //监控
            ShortLinkStatsRecordDTO statsRecord = buildLinkStatsRecordAndSetUser(full_short_url, request, response);
            shortLinkStats(statsRecord);
            HttpServletResponse response1 = (HttpServletResponse) response;
            response1.sendRedirect(origin_url);
            return;
        }
        // 判断布隆过滤器是否存在 完整短连接， 这个full_short_url 在添加短连接的时候就添加到布隆过滤器里面了
        // 这个避免了 穿透 乱输入的链接地址 PS : 误判的话，逻辑向下走 通过redis的路由表 在判断一次
        boolean contains = shortUriCreateCachePenetrationBloomFilter.contains(full_short_url);
        if(!contains) {
            ((HttpServletResponse) response).sendRedirect("/page/notfound");
            return;
        }
        //缓存的路由信息 存在
        String isNoShortGotoLink = stringRedisTemplate.opsForValue().get(String.format(GOTO_IS_NOLL_SHORT_LINK_KEY, full_short_url));
        if(StrUtil.isNotBlank(isNoShortGotoLink)) {
            ((HttpServletResponse) response).sendRedirect("/page/notfound");
            return;
        }
        RLock lock = redissonClient.getLock(String.format(LOCK_GOTO_SHORT_LINK_KEY, full_short_url));
        lock.lock();
        try {
            origin_url = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, full_short_url));
            if(StrUtil.isNotBlank(origin_url)) {
                //监控
                ShortLinkStatsRecordDTO statsRecord = buildLinkStatsRecordAndSetUser(full_short_url, request, response);
                shortLinkStats(statsRecord);
                HttpServletResponse response1 = (HttpServletResponse) response;
                response1.sendRedirect(origin_url);
                return;
            }
            //根据 full_short_url 查找路由表
            ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(Wrappers.lambdaQuery(ShortLinkGotoDO.class)
                    .eq(ShortLinkGotoDO::getFullShortUrl, full_short_url));
            if (shortLinkGotoDO == null) {
                //    这个旨在解决布隆过滤器误判
                stringRedisTemplate.opsForValue().set(String.format(GOTO_IS_NOLL_SHORT_LINK_KEY, full_short_url),"-",30, TimeUnit.MINUTES);
                ((HttpServletResponse) response).sendRedirect("/page/notfound");
                return;
            }
            //根据路由表的Gid 和 full_short_url 查找 shortLinkDO
            ShortLinkDO shortLinkDO = baseMapper.selectOne(Wrappers.lambdaQuery(ShortLinkDO.class)
                    .eq(ShortLinkDO::getGid, shortLinkGotoDO.getGid())
                    .eq(ShortLinkDO::getEnableStatus, 0)
                    .eq(ShortLinkDO::getDelFlag, 0)
                    .eq(ShortLinkDO::getFullShortUrl, shortLinkGotoDO.getFullShortUrl()));
            if (shortLinkDO == null || (shortLinkDO.getValidDate() != null && shortLinkDO.getValidDate().before(new Date()))){
                //这是解决短链接 已经过期的问题 为null 问题
                stringRedisTemplate.opsForValue().set(String.format(GOTO_IS_NOLL_SHORT_LINK_KEY, full_short_url),"-",30, TimeUnit.MINUTES);
                ((HttpServletResponse) response).sendRedirect("/page/notfound");
                return;
            }
                stringRedisTemplate.opsForValue().set(
                        String.format(GOTO_SHORT_LINK_KEY, full_short_url),
                        shortLinkDO.getOriginUrl(),
                        LinkUtil.getLinCacheValidDate(shortLinkDO.getValidDate()),
                        TimeUnit.MILLISECONDS
                );
                //监控
                ShortLinkStatsRecordDTO statsRecord = buildLinkStatsRecordAndSetUser(full_short_url, request, response);
                shortLinkStats(statsRecord);
                HttpServletResponse response1 = (HttpServletResponse) response;
                response1.sendRedirect(shortLinkDO.getOriginUrl());
        } finally {
            lock.unlock();
        }
    }

    /**
     * 短链接批量创建
     * @param requestParam
     * @return
     */
    @Override
    public ShortLinkBatchCreateRespDTO batchCreateShortLink(ShortLinkBatchCreateReqDTO requestParam) {
        List<String> originUrls = requestParam.getOriginUrls();
        List<String> describes = requestParam.getDescribes();
        List<ShortLinkBaseInfoRespDTO> result = new ArrayList<>();
        for (int i = 0; i < originUrls.size(); i++) {
            ShortLinkCreateReqDTO shortLinkCreateReqDTO = BeanUtil.toBean(requestParam, ShortLinkCreateReqDTO.class);
            shortLinkCreateReqDTO.setOriginUrl(originUrls.get(i));
            shortLinkCreateReqDTO.setDescribe(describes.get(i));
            try {
                ShortLinkCreateRespDTO shortLink = createShortLink(shortLinkCreateReqDTO);
                ShortLinkBaseInfoRespDTO linkBaseInfoRespDTO = ShortLinkBaseInfoRespDTO.builder()
                        .fullShortUrl(shortLink.getFullShortUrl())
                        .originUrl(shortLink.getOriginUrl())
                        .describe(describes.get(i))
                        .build();
                result.add(linkBaseInfoRespDTO);
            } catch (Throwable ex) {
                log.error("批量创建短链接失败，原始参数：{}", originUrls.get(i));
            }
        }
        return ShortLinkBatchCreateRespDTO.builder()
                .total(result.size())
                .baseLinkInfos(result)
                .build();
    }

    /**
     *  生成监控短连接的一些基础信息
     * @param fullShortUrl
     * @param request
     * @param response
     * @return
     */
    private ShortLinkStatsRecordDTO buildLinkStatsRecordAndSetUser(String fullShortUrl, ServletRequest request, ServletResponse response) {
        AtomicBoolean uvFirstFlag = new AtomicBoolean();
        Cookie[] cookies = ((HttpServletRequest) request).getCookies();
        AtomicReference<String> uv = new AtomicReference<>();
        // 这个线程主要是创建一个 cookie 同时放到缓存 set 里面
        Runnable addResponseCookieTask = () -> {
            uv.set(UUID.fastUUID().toString());
            Cookie uvCookie = new Cookie("uv", uv.get());
            uvCookie.setMaxAge(60 * 60 * 24 * 30);
            // 符合路径的 url 才会携带 cookie 到服务端
            uvCookie.setPath(StrUtil.sub(fullShortUrl, fullShortUrl.indexOf("/"), fullShortUrl.length()));
            ((HttpServletResponse) response).addCookie(uvCookie);
            uvFirstFlag.set(Boolean.TRUE);
            stringRedisTemplate.opsForSet().add(SHORT_LINK_STATS_UV_KEY + fullShortUrl, uv.get());
        };
        if (ArrayUtil.isNotEmpty(cookies)) {
            Arrays.stream(cookies)
                    .filter(each -> Objects.equals(each.getName(), "uv"))
                    .findFirst()
                    .map(Cookie::getValue)
                    .ifPresentOrElse(each -> {
                        uv.set(each);
                        Long uvAdded = stringRedisTemplate.opsForSet().add(SHORT_LINK_STATS_UV_KEY + fullShortUrl, each);
                        uvFirstFlag.set(uvAdded != null && uvAdded > 0L);
                    }, addResponseCookieTask);
        } else {
            addResponseCookieTask.run();
        }
        String remoteAddr = LinkUtil.getActualIP(((HttpServletRequest) request));
        String os = LinkUtil.getOs(((HttpServletRequest) request));
        String browser = LinkUtil.getBrowser(((HttpServletRequest) request));
        String device = LinkUtil.getDevice(((HttpServletRequest) request));
        String network = LinkUtil.getNetwork(((HttpServletRequest) request));
        Long uipAdded = stringRedisTemplate.opsForSet().add(SHORT_LINK_STATS_UIP_KEY + fullShortUrl, remoteAddr);
        boolean uipFirstFlag = uipAdded != null && uipAdded > 0L;
        return ShortLinkStatsRecordDTO.builder()
                .fullShortUrl(fullShortUrl)
                .uv(uv.get())
                .uvFirstFlag(uvFirstFlag.get())
                .uipFirstFlag(uipFirstFlag)
                .remoteAddr(remoteAddr)
                .os(os)
                .browser(browser)
                .device(device)
                .network(network)
                .build();
    }

    /**
     * 记录基础访问监控数据
     * @param statsRecord
     */
    @Override
    public void shortLinkStats(ShortLinkStatsRecordDTO statsRecord) {
        Map<String, String> producerMap = new HashMap<>();
        producerMap.put("statsRecord", JSON.toJSONString(statsRecord));
        shortLinkStatsSaveProducer.send(producerMap);
    }

    /**
     * todo 获取网站图标  和讲的不一样
     * @param url
     * @return
     */
    @SneakyThrows
    private String getFavicon(String url) {
        URL targetUrl = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection();
        connection.setRequestMethod("GET");
        connection.connect();
        int responseCode = connection.getResponseCode();
        if (HttpURLConnection.HTTP_OK == responseCode) {
            Document document = Jsoup.connect(url).get();
            Element faviconLink = document.select("link[rel~=(?i)^(shortcut )?icon]").first();
            if (faviconLink != null) {
                return faviconLink.attr("abs:href");
            }
        }
        return null;
    }
}